package com.dukk.gis.tools.extensions.geohash;

import ch.hsr.geohash.BoundingBox;
import ch.hsr.geohash.GeoHash;
import ch.hsr.geohash.WGS84Point;
import ch.hsr.geohash.util.BoundingBoxGeoHashIterator;
import ch.hsr.geohash.util.TwoGeoHashBoundingBox;
import com.dukk.gis.tools.GeoTool;
import com.dukk.gis.tools.WktTool;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;

import java.util.HashSet;
import java.util.Set;


/**
 * geo Hash工具类
 *
 * Digits and precision in km
 * geohash length	lat(纬度) bits	lng(经度) bits	lat error	lng error	~km error
 * 1	2	3	±23	±23	±2500
 * 2	5	5	±2.8	±5.6	±630
 * 3	7	8	±0.70	±0.70	±78
 * 4	10	10	±0.087	±0.18	±20
 * 5	12	13	±0.022	±0.022	±2.4
 * 6	15	15	±0.0027	±0.0055	±0.61
 * 7	17	18	±0.00068	±0.00068	±0.076
 * 8	20	20	±0.000085	±0.00017	±0.019
 *
 *
 * geohash所表示的矩形区域大小
 * Geohash 长度	宽（Width）	高（Height）
 * 1	4604.5 km	5003.8 km
 * 2	1249.4 km	625.5 km
 * 3	156.4 km	156.4 km
 * 4	39.1 km	19.5 km
 * 5	4.9 km	4.9 km
 * 6	1.2 km	610.8 m
 * 7	152.7 m	152.7 m
 * 8	38.2 m	19.1 m
 * 9	4.8 m	4.8 m
 * 10	1.2 m	596.5 mm
 * 11	149.1 mm	149.1 mm
 * 12	37.3 mm	18.6 mm
 *
 */
public class GeoHashTool {

    private int characterPrecision = 8; //默认12位


    public GeoHashTool(){}

    public GeoHashTool(int characterPrecision){
        this.characterPrecision = characterPrecision;
    }


    public String geoHash(Point point){
        double x = point.getX();
        double y = point.getY();
        return geoHash(x, y);
    }

    public String geoHash(Coordinate coordinate){
        double x = coordinate.getX();
        double y = coordinate.getY();
        return geoHash(x, y);
    }

    public String geoHash(double x, double y){
        return GeoHash.geoHashStringWithCharacterPrecision(y,x, this.characterPrecision);
    }

    public Set<String> geoHash(LineString lineString){
        Set<String> geoHashSet = new HashSet<>();
        Coordinate[] coordinates = lineString.getCoordinates();
        for (Coordinate coordinate : coordinates) {
            geoHashSet.add(geoHash(coordinate));
        }
        return geoHashSet;
    }

    public Set<String> geoHash(MultiLineString multiLineString){
        int numGeometries = multiLineString.getNumGeometries();
        Set<String> geoHashSet = new HashSet<>();
        for(int i=0; i<numGeometries;i++){
           Geometry lineString = multiLineString.getGeometryN(i);
           geoHashSet.addAll(geoHash((LineString) lineString));
        }
        return geoHashSet;
    }

    public Set<String> geoHash(MultiPoint multiPoint){
        Set<String> geoHashSet = new HashSet<>();
        int num = multiPoint.getNumGeometries();
        for (int i=0 ; i < num; i++){
            Geometry geometry = multiPoint.getGeometryN(i);
            geoHashSet.add(geoHash((Point) geometry));
        }
        return geoHashSet;
    }


    public Set<String> geoHash(Polygon polygon){
        HashSet<String> geoHashSet = new HashSet<>();
        Coordinate[] coordinates = polygon.getCoordinates();
        for(Coordinate coordinate : coordinates){
            geoHashSet.add(geoHash(coordinate));
        }
        return geoHashSet;
    }

    public Set<String> geoHash(MultiPolygon multiPolygon){
        HashSet<String> geoHashSet = new HashSet<>();
        int num = multiPolygon.getNumGeometries();
        for(int i=0; i<num; i++){
            Geometry geometry = multiPolygon.getGeometryN(i);
            geoHashSet.addAll(geoHash((Polygon) geometry));
        }
        return geoHashSet;
    }

    public Set<String> geoHash(Geometry geometry) throws ClassNotFoundException{
        Set<String> geoHashSet = new HashSet<>();
        if(geometry instanceof Point){
            geoHashSet.add(geoHash((Point) geometry));
        } else if (geometry instanceof  MultiPoint) {
            geoHashSet.addAll(geoHash((MultiPoint) geometry));
        } else if (geometry instanceof LineString) {
            geoHashSet.addAll(geoHash((LineString) geometry));
        } else if (geometry instanceof MultiLineString) {
            geoHashSet.addAll(geoHash((MultiLineString) geometry));
        } else if (geometry instanceof Polygon) {
            geoHashSet.addAll(geoHash((Polygon) geometry));
        } else if (geometry instanceof MultiPolygon) {
            geoHashSet.addAll(geoHash((MultiPolygon) geometry));
        }else {
            throw new ClassNotFoundException("geometry类型不确定:"+geometry.getGeometryType());
        }
        return geoHashSet;
    }

    public GeoHash decode(String geoHash){
        return GeoHash.fromGeohashString(geoHash);
    }


    public BoundingBox boundingBox(String geoHash){
        return decode(geoHash).getBoundingBox();
    }

    public BoundingBox boundingBox(GeoHash geoHash){
        return geoHash.getBoundingBox();
    }

    /**
     * 获取 geohash网格
     * @param geoHash
     * @return
     */
    public Geometry boxGeometry(GeoHash geoHash){

        Coordinate[] coordinates = new Coordinate[5];

        BoundingBox boundingBox = boundingBox(geoHash);
        WGS84Point northEastCorner = boundingBox.getNorthEastCorner();
        WGS84Point northWestCorner = boundingBox.getNorthWestCorner();
        WGS84Point southWestCorner = boundingBox.getSouthWestCorner();
        WGS84Point southEastCorner = boundingBox.getSouthEastCorner();

        coordinates[0] = GeoTool.createCoordinate(northEastCorner.getLongitude(), northEastCorner.getLatitude());
        coordinates[1] = GeoTool.createCoordinate(northWestCorner.getLongitude(), northWestCorner.getLatitude());
        coordinates[2] = GeoTool.createCoordinate(southWestCorner.getLongitude(), southWestCorner.getLatitude());
        coordinates[3] = GeoTool.createCoordinate(southEastCorner.getLongitude(), southEastCorner.getLatitude());
        coordinates[4] = GeoTool.createCoordinate(northEastCorner.getLongitude(), northEastCorner.getLatitude());

        return GeoTool.createPolygon(coordinates);

    }

    /**
     * 获取geohash网格
     * @param geoHash
     * @return
     */
    public Geometry boxGeometry(String geoHash){
       return boxGeometry(decode(geoHash));
    }





    public static void main(String[] args) throws ParseException, ClassNotFoundException {
        double x = 116.2613408;
        double y = 39.9077535;


        System.out.println(GeoHash.withBitPrecision(y,x,16).toBinaryString());

        String geoHash12 = GeoHash.geoHashStringWithCharacterPrecision(y, x, 12);
        String geoHash11 = GeoHash.geoHashStringWithCharacterPrecision(y, x, 11);
        String geoHash10 = GeoHash.geoHashStringWithCharacterPrecision(y, x, 10);
        String geoHash9 = GeoHash.geoHashStringWithCharacterPrecision(y, x, 9);
        System.out.println(geoHash12);
        System.out.println(geoHash11);
        System.out.println(geoHash10);
        System.out.println(geoHash9);

        GeoHash geoHash = GeoHash.fromGeohashString(geoHash12);
        BoundingBox boundingBox = geoHash.getBoundingBox();
        System.out.println(boundingBox.toString());


        GeoHashTool geoHashTool = new GeoHashTool();

       Geometry geometry = WktTool.wktToGeo("LINESTRING (115.835 36.014, 115.893 36.019)");
       Set<String> geoHashSet = geoHashTool.geoHash(geometry);

       GeoHash g1 = null;
       GeoHash g2 = null;
       int i=0;
       for (String h : geoHashSet){

           System.out.println(geoHashTool.boxGeometry(h));

           if(i == 0){
               g1 = geoHashTool.decode(h);

           }
           if(i == 1){
               g2 = geoHashTool.decode(h);
           }
           i++;
       }

      /*  System.out.println(geoHashTool.boxGeometry(g1));
        System.out.println(geoHashTool.boxGeometry(g2));

        TwoGeoHashBoundingBox twoGeoHashBoundingBox = new TwoGeoHashBoundingBox(g2, g1);
        BoundingBoxGeoHashIterator boundingBoxGeoHashIterator = new BoundingBoxGeoHashIterator(twoGeoHashBoundingBox);

        System.out.println("######################");
        while (boundingBoxGeoHashIterator.hasNext()){
           GeoHash geoHash1 = boundingBoxGeoHashIterator.next();
            System.out.println(geoHashTool.boxGeometry(geoHash1));
        }*/

        System.out.println(geoHashTool.boxGeometry(g1));
        TwoGeoHashBoundingBox twoGeoHashBoundingBox = TwoGeoHashBoundingBox.withCharacterPrecision(g1.getBoundingBox(), 8);

        BoundingBoxGeoHashIterator boundingBoxGeoHashIterator = new BoundingBoxGeoHashIterator(twoGeoHashBoundingBox);

        System.out.println("######################");
        while (boundingBoxGeoHashIterator.hasNext()){
            GeoHash geoHash1 = boundingBoxGeoHashIterator.next();
            System.out.println(geoHashTool.boxGeometry(geoHash1));
        }

    }
}
